# -*- coding: utf-8 -*-
"""
Module Description: 事务相关
Date: 2018/8/9
Author:Bai Jin Ping
"""
import copy
import threading

from framework.dal.orm import get_db_name
from utils.traceback_utils import print_exc


class TransactionPropagation(object):
    """
    事务传播规则类型(参考Spring框架)

    REQUIRED: 若当前有事务,则添加到该事务,否则新建一个事务
    REQUIRES_NEW: 不论当前是否有事务,都新建一个事务.事务处理结果不影响外部事务.
    NOT_SUPPORTED: 不论当前是否有事务,内部操作都直接提交
    """
    REQUIRED = 1

    # 使用时需特别注意,见test_dal.py::TestTransaction::test_propagation_requires_new::(REQUIRES_NEW事务是否提前提交)
    REQUIRES_NEW = 2

    # 暂时不支持
    NOT_SUPPORTED = 3

class CommitNotCurrentTransactionError(Exception):
    """在一系列嵌套事务中, 只能够按照嵌套顺序逐层提交"""


class RollbackNotCurrentTransactionError(Exception):
    """在一系列嵌套事务中, 只能够按照嵌套顺序逐层回滚"""


class Transaction(object):
    """
    # with方式调用事务: with块内异常不会提交事务
    >>> with Transaction() as tx:
    >>>     # do something...
    >>> tx.is_success()  # 事务是否成功

    # with方式的回滚:
    >>> with Transaction() as tx:
     >>>    # do something
     >>>    tx.rollback()
     >>> tx.is_success()    # False

    # ~~一般方式调用事务~~[这种方式无法简单处理嵌套事务的回滚,因此v2.5开始被弃用]:
    >>> tx = Transaction()  # 也可以使用 Transaction(begin=True)省略下一行tx.begin()代码
    >>> tx.begin()
    >>> # do something...
    >>> tx.commit()
    >>> tx.is_success()  # 事务是否成功
    """

    def __init__(self, reraise_error=True, propagation=TransactionPropagation.REQUIRED):
        """
        :param reraise_error: 是否抛出异常到事务外
        :param begin: 是否立刻开启事务
        :param propagation: 事务隔离级别
        """
        #############################
        # 事务传播规则
        # 值域=TransactionPropagation
        #############################
        if propagation != TransactionPropagation.REQUIRED \
                and propagation != TransactionPropagation.REQUIRES_NEW \
                and propagation != TransactionPropagation.NOT_SUPPORTED:
            raise ValueError('Unsupported Transaction Propagation!')

        self.propagation = propagation

        # 是否直接抛出事务异常
        self._do_reraise_error = reraise_error

        # 是否已经打开事务
        self._after_begin = False

        # 是否已提交
        self._already_commit = False

        # 是否已回滚
        self._already_rollback = False

        #############################
        # 使用context manager风格的事务写法能够调用的方法与一般风格的不同
        # 需要在事务对象处于不同状态时对能够进行的操作(主要是begin,commit和rollback)进行限定
        # context manager风格: 不允许begin,commit方法的显式调用
        # 一般风格: 不允许__enter__,__exit__的调用
        #
        # 一般风格的事务写法已被禁用
        #############################
        # 是否使用context manager(资源管理器)风格的事务写法
        # 在_before_begin()中设为False
        # 在_before_enter()中设为True
        # self._use_ctx_mgr_style = None

        #############################
        # 在事务 未提交/回滚/事务提交失败 的情况下,该函数不会执行
        #############################
        # 在事务成功提交后执行的函数和形参列表
        self._after_success_commit_process = [
            # (func, args, kwargs)
        ]

        # 事务出错是否抛出异常，抛出异常则外部事务不在执行，不抛出异常，外部事务可以继续执行
        self.is_commit_raise_error = True

        # 事务改动记录
        self.trans_list = []

        # 父事务对象,用于支持嵌套事务
        self._parent = None
        # 是否子嵌套事务
        self._is_nested = False

        # 事务是否成功提交
        self._success = False

        # 事务是否已经回滚
        self._already_rollback = False

    def _after_success_commit(self):
        """事务成功提交后的处理"""
        if not self._already_commit or self._already_rollback:
            return

        if not self.is_success():
            return

        # 若当前事务传播级别是REQUIRED且父事务不为空, 不执行提交后的处理函数, 延迟到父事务成功提交才执行
        if self.propagation == TransactionPropagation.REQUIRED and self.parent is not None:
            return

        # 执行成功提交后的处理函数, 即使有函数执行报错, 也不会中断其他函数的执行
        for func, args, kwargs in self._after_success_commit_process:
            try:
                func(*args, **kwargs)
            except Exception as e:
                print_exc(e)

    @property
    def _db(self):
        return get_db_name()

    @property
    def parent(self):
        """获取嵌套事务的父事务对象"""
        return self._parent

    def _set_parent(self, parent_tx):
        """设置父事务对象"""
        self._parent = parent_tx
        self._is_nested = True

    def is_success(self):
        """
        检查事务是否成功提交
        :rtype bool
        :return 事务是否成功提交
        """
        return self._success

    def is_reraise_error(self):
        """
        :rtype bool
        :return: 是否在事务执行失败时抛出异常
        """
        return self._do_reraise_error

    #############################
    # 事务操作方法
    #############################
    def commit(self):
        if self._already_commit:
            return self._success

        try:
            self._commit()

            self._after_success_commit()
            return self._success
        except Exception as e:
            self._success = False

            if self.is_reraise_error():
                raise
            else:
                print_exc(e)

            return False

    def rollback(self):
        if self._already_rollback:
            return
        self._rollback()

    def __enter__(self):
        self._begin_tx()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not self._after_begin:
            raise RuntimeError('Can not exit transaction before begin transaction!')
        if self._already_commit or self._already_rollback:
            return

        try:
            # 如果事务块内程序出异常,就回滚事务
            if exc_tb is None:
                self._commit()
            else:
                self._rollback()

            if exc_type is None:
                self._after_success_commit()

        except Exception as e:
            self._success = False

            if self.is_reraise_error():
                raise
            else:
                print_exc(e)

    def on_commit(self, func, *args, **kwargs):
        """
        注册事务成功提交后的处理函数(事务延迟函数)

        若事务延迟函数抛出异常,不影响其他事务延迟函数的执行, 也不抛出异常导致逻辑中断, 只会打印错误日志
        **此时事务已经提交, 应当认为操作已经完成
        :param func: 成功提交后的处理函数
        :param args: 参数
        :param kwargs: 参数
        :return:
        """
        if not callable(func):
            raise ValueError('Function on commit must callable!')

        self._after_success_commit_process.append((func, args, kwargs))

    def _init_transaction_state(self):
        """事务开启时,尝试初始化事务状态上下文"""
        tx_state = transactin_context_storage.ctx

        # 当前事务对象是否最外层对象
        if tx_state is None:
            # 初始化事务状态上下文
            tx_state = TransactionState(self)
            tx_state.register()
        # 当前事务对象是子事务对象
        else:
            # 设置父事务对象
            self._set_parent(tx_state.cur_tx)
            # 作为子事务对象传入
            tx_state.push_nest_tx(self)

    def _begin_tx(self):
        """开始事务"""
        if self._after_begin:
            raise RuntimeError('Already begin transaction!')
        self._after_begin = True

        self._init_transaction_state()

        transaction_state = transactin_context_storage.ctx

        if self._parent is not None:
            if self.propagation == TransactionPropagation.REQUIRED:
                # 完整复制上一层父事务对象的改动记录, 相当于汇总后作为当前事务的当前状态
                transaction_state.tx_change_stack.append(copy.deepcopy(transaction_state.tx_change_stack[-1]))
            elif self.propagation == TransactionPropagation.REQUIRES_NEW:
                # 独立事务 **不依赖也不会影响** 父事务对象的改动记录
                transaction_state.tx_change_stack.append({})

    def _commit(self):
        """提交事务的逻辑处理"""
        if not self._after_begin:
            raise RuntimeError('Can not commit transaction before begin transaction!')
        if self._already_commit:
            raise RuntimeError('Can not commit transaction many times!')
        if self._already_rollback:
            raise RuntimeError('Current transaction already rollback!Can not commit!')

        transaction_state = transactin_context_storage.ctx

        top_transaction = transaction_state.cur_tx
        if self is not top_transaction:
            # remote_api在这里是不处理, 而不是抛出异常
            raise CommitNotCurrentTransactionError('must commit current transaction in nest transaction stack!')

        if self.propagation == TransactionPropagation.REQUIRES_NEW:
            try:
                self._commit_transaction()
                self._already_commit = True
            except Exception as e:
                self._rollback()

                if self.is_commit_raise_error:
                    raise
                else:
                    # 不抛出异常, 外部事务可以继续提交
                    print_exc(e)
            finally:
                # 独立事务结束时,必须移除自己的独立改动记录
                if len(transaction_state.tx_change_stack) > 0:
                    transaction_state.tx_change_stack.pop()

        elif self.propagation == TransactionPropagation.REQUIRED:
            # 是否还不是顶层事务对象
            if self.parent is not None:
                # 当前事务马上要从事务对象栈顶移除
                # 1.要将当前事务改动传递给父事务
                self.parent.trans_list.extend(self.trans_list)
                # 2.要将当前事务的on_commit方法参数列表传递给父事务
                self.parent._after_success_commit_process.extend(self._after_success_commit_process)
                self._success = True

            # 已经是顶层事务对象, 要最终提交事务改动
            else:
                try:
                    self._commit_transaction()
                    self._already_commit = True
                except Exception as e:
                    # 事务执行失败, 回滚,会同时弹出
                    self._rollback()

                    raise

        # 弹出当前事务对象
        transaction_state.commit_tx(self)

    def _rollback(self):
        """
        回滚事务
        从事务对象列表中删除自身改动
        """
        # 回滚事务前的判断
        if not self._after_begin:
            raise RuntimeError('Can not rollback transaction before begin transaction!')
        if self._already_commit:
            raise RuntimeError('Current transaction already commit!Can not rollback!')
        if self._already_rollback:
            raise RuntimeError('Can not rollback transaction many times!')
        self._already_rollback = True

        transaction_state = transactin_context_storage.ctx

        transaction_state.rollback_tx(self)

    def _commit_transaction(self):
        """具体提交事务操作"""
        for event in self.trans_list:
            event['pk'] = ','.join(event['pk'])

        # 如果self._trans_list为空则不提交事务，直接返回成功
        if self.trans_list:
            from framework.dal.orm.controller_api import ORMController
            self._success = ORMController.commit_transaction(self, self.trans_list)
            return self._success
        else:
            self._success = True
            return self._success

    def transaction_append(self, arg, update_value=None):
        """
        update_value 由于更新操作只保留具体操作，所以把整个模型的值也传进来，不然在事务中查询时，
        如果该记录未在数据库中找到,会缺少其他数据,
        例如更新model.id=7, 此时去数据库是找不到model.id=7的这个model数据的，
        arg[
            {"orm": "对象名", "pk": "主键名,...", "action": "insert", "body": {"values": {"字段名": 字段值, ...}}},
            {"orm": "对象名", "pk": "主键名,...", "action": "delete", "body": {"字段名__运算符": 字段值, ..., "operator": "AND[OR]"}},
            {"orm": "对象名", "pk": "主键名,...", "action": "update", "body": {"values": {"字段名": 字段值, ...},
            "conditions": {"字段名__运算符": 字段值, ...,  operator": "AND[OR]"}}},
            ...
        ]
        """
        arg["Db"] = self._db
        self.trans_list.append(arg)

        # 还要做一步,是放入t_action中用于后续查询判断使用
        t_action_trace_list = transactin_context_storage.ctx.tx_change_stack

        # 从trace层的字典取出db层的字典
        if not t_action_trace_list:
            # 如果列表是空的
            t_action_trace_list.append({})

        t_action_db = t_action_trace_list[-1].get(arg["Db"], {})
        if not t_action_db:
            t_action_trace_list[-1][arg["Db"]] = t_action_db

        # 从db层的字典取出table层的操作数组
        t_action_table = t_action_db.get(arg["orm"], {})
        if not t_action_table:
            t_action_db[arg["orm"]] = t_action_table

        # 只有全主键保存操作才记录到tx_change_stack
        if arg.get("is_all_key", False):
            if arg.get('action') == "insert":
                # 插入的可能是单条记录，可能是批量记录
                for i in arg.get("body").get("values"):
                    table_pk_value = list()
                    for k in arg.get("pk"):
                        table_pk_value.append(str(i.get(k)))
                    table_pk = "---".join(table_pk_value)
                    pk_dict = dict()
                    pk_dict["action"] = arg["action"]
                    pk_dict.update({"value": i})
                    t_action_table.update({table_pk: pk_dict})

            elif arg.get('action') == "update":
                table_pk_value = list()
                for k in arg.get("pk"):
                    table_pk_value.append(str(arg.get("body").get("conditions").get(k + "__eq")))
                table_pk = "---".join(table_pk_value)
                if update_value is not None:
                    pk_dict = dict()
                    pk_dict["action"] = arg["action"]
                    pk_dict.update({"value": update_value})
                    t_action_table.update({table_pk: pk_dict})

                else:
                    pk_dict = dict()
                    pk_dict["action"] = arg["action"]
                    pk_dict.update({"value": arg.get("body").get("values")})
                    t_action_table.update({table_pk: pk_dict})
                # # 不能直接更新
                # # 如果已经有了
                # if t_action_table.get(table_pk, None):
                #     pk_dict = t_action_table.get(table_pk)
                #     pk_dict["action"] = arg["action"]
                #     pk_dict["value"].update(arg.get("body").get("values"))
                # else:
                #     pk_dict = dict()
                #     pk_dict["action"] = arg["action"]
                #     pk_dict.update({"value": arg.get("body").get("values")})
                #     t_action_table.update({table_pk: pk_dict})

            elif arg.get('action') == "delete":
                table_pk_value = list()
                for k in arg.get("pk"):
                    table_pk_value.append(str(arg.get("body").get(k+"__eq")))
                table_pk = "---".join(table_pk_value)
                pk_dict = dict()
                pk_dict["action"] = arg["action"]
                pk_dict["value"] = {}
                t_action_table.update({table_pk: pk_dict})


class TransactionState(object):
    """
    事务状态数据: 保存同一个嵌套事务集内的共享数据

    创建事务时会同时创建, 作为最外层事务对象的属性, 并保存到TransactionContextStorage用于告知各方当前处于一个嵌套事务中
    在这个嵌套事务内新建的事务, 都会被作为最外层事务对象的子事务.
    直到最外层事务提交/回滚时, 事务状态数据/TransactionContextStorage才会被置空.
    """
    def __init__(self, owner_transaction):
        """
        :param owner_transaction: 最外层事务对象
        """
        # !事务对象栈tx_stack 和 事务改动记录栈tx_change_stack 长度 **不一定** 保持一致

        # 事务对象栈, 从左向右延伸
        self.tx_stack = []

        # 事务改动记录栈
        self.tx_change_stack = []

        self.owner_transaction = owner_transaction
        self.tx_stack.append(owner_transaction)
        self.tx_change_stack.append({})

    def register(self):
        """将事务状态对象注册到线程本地变量存储"""
        # 作为最外部事务对象传入
        transactin_context_storage.set_current_ctx(self)

    def logout(self):
        """将事务状态对象从线程本地变量存储注销"""
        transactin_context_storage.clear_current_ctx(self)

    def push_nest_tx(self, nest_tx):
        """
        添加新的嵌套事务
        :param nest_tx:
        :return:
        """
        self.tx_stack.append(nest_tx)

    def commit_tx(self, tx):
        """
        提交事务对象
        :param tx: 进行提交的事务对象
        :return:
        """
        assert tx is self.cur_tx

        # 最外层事务对象提交后清理事务上下文
        if len(self.tx_stack) == 1:
            self.logout()

        top_tx = self.tx_stack.pop()
        return top_tx

    def rollback_tx(self, tx):
        """
        回滚事务

        只能一层一层回滚
        :param tx: 被回滚的事务对象
        """
        assert tx is self.cur_tx

        # 最外层事务对象提交后清理事务上下文
        if len(self.tx_stack) == 1:
            self.logout()

        # 从事务改动栈移除该事务的所有改动记录(包括其内的子事务改动)
        cur_tx_stack_idx = self.tx_stack.index(tx)
        for _ in xrange(len(self.tx_change_stack) - cur_tx_stack_idx):
            self.tx_change_stack.pop()

        self.tx_stack.pop()

    @property
    def cur_tx(self):
        """获得当前栈顶的事务对象"""
        if self.tx_stack:
            return self.tx_stack[-1]

    def is_tx_stack_empty(self):
        """事务栈是否已空"""
        return len(self.tx_stack) == 0


class TransactionContextStorage(threading.local):
    """
    事务上下文存储(线程本地变量)
    """
    initialized = False

    # 事务状态对象TransactionState
    ctx = None

    def __init__(self, **kw):
        if self.initialized:
            raise SystemError('__init__ called too many times')

        self.initialized = True
        self.__dict__.update(kw)

    def set_current_ctx(self, ctx):
        """设置当前事务对象"""
        # TransactionState不允许嵌套
        assert self.ctx is None

        self.ctx = ctx

    def clear_current_ctx(self, ctx):
        """清除当前上下文"""
        # 必须是最外层事务对象才能够移除事务上下文对象
        assert self.ctx is not None
        assert self.ctx.cur_tx.parent is None
        assert self.ctx is ctx

        self.ctx = None


# 数据库事务上下文
transactin_context_storage = TransactionContextStorage()
